var TabInTabService = {
    NSResolver : {
        lookupNamespaceURI : function(aPrefix)
        {
            switch (aPrefix)
            {
                case 'xul':
                    return 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
                case 'html':
                case 'xhtml':
                    return 'http://www.w3.org/1999/xhtml';
                case 'xlink':
                    return 'http://www.w3.org/1999/xlink';
                default:
                    return '';
            }
        }
    },

    init : function()
    {
        if (!('gBrowser' in window)) return;
        
        if (this.initialized) return;
        this.initialized = true;

        window.removeEventListener('load', this, false);
        window.addEventListener('unload', this, false);

        this.initTabBrowser(gBrowser);
    },
    initialized : false,

    initTabBrowser : function(aTabBrowser)
    {
        if (aTabBrowser.localName != 'tabbrowser') return;
        aTabBrowser.tabInTab = new TabInTabBrowser(aTabBrowser);
        aTabBrowser.tabInTab.init();
    },

    destroy : function()
    {
        window.removeEventListener('unload', this, false);
    },

    handleEvent : function(aEvent)
    {
        switch (aEvent.type)
        {
            case 'load':
                this.init();
                return;

            case 'unload':
                this.destroy();
                return;
        }
    },

    /* get tab(s). adapted from TreeStyleTab */ 
    
    getTabById : function(aId, aTabBrowserChildren) 
    {
        if (!aId) return null;
        var b = aTabBrowserChildren ? this.getTabBrowserFromChild(aTabBrowserChildren) : null ;
        if (!b) b = this.browser;
        return this.evaluateXPath(
                'descendant::xul:tab[@'+this.kID+' = "'+aId+'"]',
                b.mTabContainer,
                XPathResult.FIRST_ORDERED_NODE_TYPE
            ).singleNodeValue;
    },
 
    getTabs : function(aTabBrowser) 
    {
        return this.evaluateXPath(
                'descendant::xul:tab',
                aTabBrowser.mTabContainer
            );
    },
 
    getTabsArray : function(aTabBrowser) 
    {
        var tabs = this.getTabs(aTabBrowser);
        var array = [];
        for (var i = 0, maxi = tabs.snapshotLength; i < maxi; i++)
        {
            array.push(tabs.snapshotItem(i));
        }
        return array;
    },
 
    getFirstTab : function(aTabBrowser) 
    {
        return this.evaluateXPath(
                'descendant::xul:tab[1]',
                aTabBrowser.mTabContainer,
                XPathResult.FIRST_ORDERED_NODE_TYPE
            ).singleNodeValue;
    },
 
    getLastTab : function(aTabBrowser) 
    {
        return this.evaluateXPath(
                'descendant::xul:tab[last()]',
                aTabBrowser.mTabContainer,
                XPathResult.FIRST_ORDERED_NODE_TYPE
            ).singleNodeValue;
    },
 
    getNextTab : function(aTab) 
    {
        return this.evaluateXPath(
                'following-sibling::xul:tab[1]',
                aTab,
                XPathResult.FIRST_ORDERED_NODE_TYPE
            ).singleNodeValue;
    },
 
    getPreviousTab : function(aTab) 
    {
        return this.evaluateXPath(
                'preceding-sibling::xul:tab[1]',
                aTab,
                XPathResult.FIRST_ORDERED_NODE_TYPE
            ).singleNodeValue;
    },
 
    getNextVisibleTab : function(aTab) 
    {
        return this.evaluateXPath(
                'following-sibling::xul:tab[not(@'+this.kCOLLAPSED+'="true")][1]',
                aTab,
                XPathResult.FIRST_ORDERED_NODE_TYPE
            ).singleNodeValue;
    },
 
    getPreviousVisibleTab : function(aTab) 
    {
        return this.evaluateXPath(
                'preceding-sibling::xul:tab[not(@'+this.kCOLLAPSED+'="true")][1]',
                aTab,
                XPathResult.FIRST_ORDERED_NODE_TYPE
            ).singleNodeValue;
    },
 
    getLastVisibleTab : function(aTab) 
    {
        return this.evaluateXPath(
                'child::xul:tab[not(@'+this.kCOLLAPSED+'="true")][last()]',
                aTab.parentNode,
                XPathResult.FIRST_ORDERED_NODE_TYPE
            ).singleNodeValue;
    },
 
    getVisibleTabs : function(aTab) 
    {
        var xpathResult = this.evaluateXPath(
                'child::xul:tab[not(@'+this.kCOLLAPSED+'="true")]',
                aTab.parentNode
            );
        return xpathResult;
    },
 
    getVisibleIndex : function(aTab) 
    {
        if (aTab.getAttribute(this.kCOLLAPSED) == 'true') return -1;
        return this.evaluateXPath(
                'count(preceding-sibling::xul:tab[not(@'+this.kCOLLAPSED+'="true")])',
                aTab,
                XPathResult.NUMBER_TYPE
            ).numberValue;
    },

    /* tree manipulations. adapted from TreeStyleTab */ 
    
    get rootTabs() 
    {
        return this.getArrayFromXPathResult(
                this.evaluateXPath(
                    'child::xul:tab[not(@'+this.kNEST+') or @'+this.kNEST+'="0" or @'+this.kNEST+'=""]',
                    this.browser.mTabContainer
                )
            );
    },
 
    getParentTab : function(aTab) 
    {
        if (!aTab) return null;
        var id = aTab.getAttribute(this.kID);
        if (!id) return null; // not initialized yet
        return this.evaluateXPath(
                'parent::*/child::xul:tab[contains(concat("|", @'+this.kCHILDREN+', "|"), "|'+id+'|")]',
                aTab,
                XPathResult.FIRST_ORDERED_NODE_TYPE
            ).singleNodeValue;
    },
 
    getRootTab : function(aTab) 
    {
        var parent = aTab;
        var root   = aTab;
        while (parent = this.getParentTab(parent))
        {
            root = parent;
        }
        return root;
    },
 
    getNextSiblingTab : function(aTab) 
    {
        if (!aTab) return null;

        var parentTab = this.getParentTab(aTab);

        if (!parentTab) {
            var next = aTab;
            do {
                next = next.nextSibling;
            }
            while (next &&
                    next.nodeType == Node.ELEMENT_NODE &&
                    this.getParentTab(next));
            return next;
        }

        var children = parentTab.getAttribute(this.kCHILDREN);
        if (children) {
            var list = ('|'+children).split('|'+aTab.getAttribute(this.kID))[1].split('|');
            for (var i = 0, maxi = list.length; i < maxi; i++)
            {
                var firstChild = this.getTabById(list[i], aTab);
                if (firstChild) return firstChild;
            }
        }
        return null;
    },
 
    getPreviousSiblingTab : function(aTab) 
    {
        if (!aTab) return null;

        var parentTab = this.getParentTab(aTab);

        if (!parentTab) {
            var prev = aTab;
            do {
                prev = prev.previousSibling;
            }
            while (prev &&
                    prev.nodeType == Node.ELEMENT_NODE &&
                    this.getParentTab(prev));
            return prev;
        }

        var children = parentTab.getAttribute(this.kCHILDREN);
        if (children) {
            var list = ('|'+children).split('|'+aTab.getAttribute(this.kID))[0].split('|');
            for (var i = list.length-1; i > -1; i--)
            {
                var lastChild = this.getTabById(list[i], aTab);
                if (lastChild) return lastChild;
            }
        }
        return null;
    },
 
    getChildTabs : function(aTab, aAllTabsArray) 
    {
        var tabs = [];
        if (!aTab) return null;

        var children = aTab.getAttribute(this.kCHILDREN);
        if (!children) return tabs;

        if (aAllTabsArray) tabs = aAllTabsArray;

        var list = children.split('|');
        var tab;
        for (var i = 0, maxi = list.length; i < maxi; i++)
        {
            tab = this.getTabById(list[i], aTab);
            if (!tab) continue;
            tabs.push(tab);
            if (aAllTabsArray)
                this.getChildTabs(tab, tabs);
        }

        return tabs;
    },
 
    hasChildTabs : function(aTab) 
    {
        if (!aTab) return false;
        return aTab.hasAttribute(this.kCHILDREN);
    },
 
    getDescendantTabs : function(aTab) 
    {
        var tabs = [];
        this.getChildTabs(aTab, tabs);
        return tabs;
    },
 
    getFirstChildTab : function(aTab) 
    {
        if (!aTab) return null;

        var children   = aTab.getAttribute(this.kCHILDREN);
        var firstChild = null;
        if (children) {
            var list = children.split('|');
            for (var i = 0, maxi = list.length; i < maxi; i++)
            {
                firstChild = this.getTabById(list[i], aTab);
                if (firstChild) break;
            }
        }
        return firstChild;
    },
 
    getLastChildTab : function(aTab) 
    {
        if (!aTab) return null;

        var children  = aTab.getAttribute(this.kCHILDREN);
        var lastChild = null;
        if (children) {
            var list = children.split('|');
            for (var i = list.length-1; i > -1; i--)
            {
                lastChild = this.getTabById(list[i], aTab);
                if (lastChild) break;
            }
        }
        return lastChild;
    },
 
    getLastDescendantTab : function(aTab) 
    {
        if (!aTab) return null;

        var tabs = this.getDescendantTabs(aTab);
        return tabs.length ? tabs[tabs.length-1] : null ;
    },
 
    getChildIndex : function(aTab, aParent) 
    {
        var parent = this.getParentTab(aTab);
        if (!aParent || !parent || aParent != parent) {
            parent = aTab;
            while (parent && parent != aParent)
            {
                aTab = parent;
                parent = this.getParentTab(parent);
            }
            if (parent != aParent)
                return -1;
            aParent = parent;
        }

        if (aParent) {
            var children = aParent.getAttribute(this.kCHILDREN);
            var list = children.split('|');
            var id = aTab.getAttribute(this.kID);
            for (var i = 0, maxi = list.length; i < maxi; i++)
            {
                if (list[i] == id) return i;
            }
            return -1;
        }
        else {
            var tabs = this.rootTabs;
            for (var i = 0, maxi = tabs.length; i < maxi; i++)
            {
                if (tabs[i] == aTab) return i;
            }
        }
    },
 
    getXOffsetOfTab : function(aTab) 
    {
        return this.evaluateXPath(
            'sum((self::* | preceding-sibling::xul:tab[not(@'+this.kCOLLAPSED+'="true")])/attribute::'+this.kX_OFFSET+')',
            aTab,
            XPathResult.NUMBER_TYPE
        ).numberValue;
    },
    getYOffsetOfTab : function(aTab)
    {
        return this.evaluateXPath(
            'sum((self::* | preceding-sibling::xul:tab[not(@'+this.kCOLLAPSED+'="true")])/attribute::'+this.kY_OFFSET+')',
            aTab,
            XPathResult.NUMBER_TYPE
        ).numberValue;
    },

    /* Utilities. adapted from TreeStyleTab */ 
    
    isEventFiredOnTwisty : function(aEvent) 
    {
        var tab = this.getTabFromEvent(aEvent);
        if (!tab) return false;

        return this.hasChildTabs(tab) && this.evaluateXPath(
                'ancestor-or-self::*[@class="'+this.kTWISTY+'" or (ancestor::xul:tabbrowser[@'+this.kMODE+'="vertical"] and @class="tab-icon")]',
                aEvent.originalTarget || aEvent.target,
                XPathResult.BOOLEAN_TYPE
            ).booleanValue;
    },
 
    isEventFiredOnClickable : function(aEvent) 
    {
        return this.evaluateXPath(
                'ancestor-or-self::*[contains(" button toolbarbutton scrollbar popup menupopup tooltip ", concat(" ", local-name(), " "))]',
                aEvent.originalTarget,
                XPathResult.BOOLEAN_TYPE
            ).booleanValue;
    },
 
    isAccelKeyPressed : function(aEvent) 
    {
        return navigator.platform.toLowerCase().indexOf('mac') > -1 ?
            (aEvent.metaKey || (aEvent.keyCode == Components.interfaces.nsIDOMKeyEvent.DOM_VK_META)) :
            (aEvent.ctrlKey || (aEvent.keyCode == Components.interfaces.nsIDOMKeyEvent.DOM_VK_CONTROL)) ;
    },
 
    get browserWindow() 
    {
        return this.WindowMediator.getMostRecentWindow('navigator:browser');
    },
 
    get browser() 
    {
        var w = this.browserWindow;
        return !w ? null :
            'SplitBrowser' in w ? w.SplitBrowser.activeBrowser :
            w.gBrowser ;
    },
 
    evaluateXPath : function(aExpression, aContext, aType) 
    {
        if (!aType) aType = XPathResult.ORDERED_NODE_SNAPSHOT_TYPE;
        try {
            var xpathResult = (aContext.ownerDocument || aContext || document).evaluate(
                    aExpression,
                    (aContext || document),
                    this.NSResolver,
                    aType,
                    null
                );
        }
        catch(e) {
            return {
                singleNodeValue : null,
                snapshotLength  : 0,
                snapshotItem    : function() {
                    return null
                }
            };
        }
        return xpathResult;
    },
 
    getArrayFromXPathResult : function(aXPathResult) 
    {
        var max = aXPathResult.snapshotLength;
        var array = new Array(max);
        if (!max) return array;

        for (var i = 0; i < max; i++)
        {
            array[i] = aXPathResult.snapshotItem(i);
        }

        return array;
    },
 
    getBoxObjectFor : function(aNode) 
    {
        return window['piro.sakura.ne.jp'].boxObject.getBoxObjectFor(aNode);
    },
 
    getTabFromEvent : function(aEvent) 
    {
        return this.getTabFromChild(aEvent.originalTarget || aEvent.target);
    },
 
    getTabFromFrame : function(aFrame, aTabBrowser) 
    {
        var b = aTabBrowser || this.browser;
        var docShell = aFrame.top
            .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
            .getInterface(Components.interfaces.nsIWebNavigation)
            .QueryInterface(Components.interfaces.nsIDocShell);
        var tabs = this.getTabs(b);
        var tab;
        for (var i = 0, maxi = tabs.snapshotLength; i < maxi; i++)
        {
            tab = tabs.snapshotItem(i);
            if (tab.linkedBrowser.docShell == docShell)
                return tab;
        }
        return null;
    },
 
    getTabFromChild : function(aTab) 
    {
        return this.evaluateXPath(
        //        'ancestor-or-self::xul:tab[ancestor::xul:tabbrowser]',
                'ancestor-or-self::xul:tab[ancestor::xul:tabbox]',
                aTab,
                XPathResult.FIRST_ORDERED_NODE_TYPE
            ).singleNodeValue;
    },
 
    getTabBrowserFromChild : function(aTab) 
    {
        if (!aTab) return null;

        if (aTab.__treestyletab__linkedTabBrowser)
            return aTab.__treestyletab__linkedTabBrowser;

        return this.evaluateXPath(
                'ancestor-or-self::xul:tabbrowser',
                aTab,
                XPathResult.FIRST_ORDERED_NODE_TYPE
            ).singleNodeValue;
    },
 
    getTabBrowserFromFrame : function(aFrame) 
    {
        var w = this.browserWindow;
        return !w ? null :
            ('SplitBrowser' in w) ? this.getTabBrowserFromChild(w.SplitBrowser.getSubBrowserAndBrowserFromFrame(aFrame.top).browser) :
            this.browser ;
    },
 
    getFrameFromTabBrowserElements : function(aFrameOrTabBrowser) 
    {
        var frame = aFrameOrTabBrowser;
        if (frame == '[object XULElement]') {
            if (frame.localName == 'tab') {
                frame = frame.linkedBrowser.contentWindow;
            }
            else if (frame.localName == 'browser') {
                frame = frame.contentWindow;
            }
            else {
                frame = this.getTabBrowserFromChild(frame);
                if (!frame) return null;
                frame = frame.contentWindow;
            }
        }
        if (!frame)
            frame = this.browser.contentWindow;

        return frame;
    },
 
    makeNewId : function() 
    {
        return 'tab-<'+Date.now()+'-'+parseInt(Math.random() * 65000)+'>';
    },
 
    makeURIFromSpec : function(aURI) 
    {
        var newURI;
        aURI = aURI || '';
        if (aURI && String(aURI).indexOf('file:') == 0) {
            var fileHandler = this.IOService.getProtocolHandler('file').QueryInterface(Components.interfaces.nsIFileProtocolHandler);
            var tempLocalFile = fileHandler.getFileFromURLSpec(aURI);
            newURI = this.IOService.newFileURI(tempLocalFile);
        }
        else {
            newURI = this.IOService.newURI(aURI, null, null);
        }
        return newURI;
    },
 
    getPropertyPixelValue : function(aElementOrStyle, aProp) 
    {
        var style = aElementOrStyle instanceof Components.interfaces.nsIDOMCSSStyleDeclaration ?
                    aElementOrStyle :
                    window.getComputedStyle(aElementOrStyle, null) ;
        return Number(style.getPropertyValue(aProp).replace(/px$/, ''));
    },

    /* get previews adapted from TabPreview */ 
    
    updatePreview: function(win, size) {
        var w = win.innerWidth;
        var h = win.innerHeight;
        var canvas = document.getElementById("tabpreviewcanvas");

        var canvasW = size.w;
        var canvasH = size.h - 5;
        // set the preview initially for top left
        var xScroll = 0;
        var yScroll = 0;
        var fudge = 0;
    
        canvas.style.width = canvasW+"px";
        canvas.style.height = canvasH+"px";
        canvas.width = canvasW;
        canvas.height = canvasH;
        try {
          var ctx = canvas.getContext("2d");
          ctx.clearRect(0, 0, canvasW, canvasH);
          ctx.save();
          ctx.scale(canvasW/w, canvasH/h);
          ctx.drawWindow(win, xScroll, yScroll, w, h+fudge, "rgb(255,255,255)");
          ctx.restore();
        } catch(e) {
            dump('Error: ' + e.message + '\n');
        }
    },
  
    getPreviewSize: function(width, win) {
        var scale, useHeight;
        var useWidth = width;
        scale = win.innerHeight / win.innerWidth;
        useHeight = useWidth * scale;
        return { w: useWidth, h: useHeight };
    },
}

window.addEventListener('load', TabInTabService, false);
